﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Sasa.QualityTools.CodeAnalysis.Core.Properties;
using Sasa.QualityTools.CodeAnalysis.Common;
using System.IO;
using Sasa.QualityTools.CodeAnalysis.Core.Utilities;
using System.Diagnostics;

namespace Sasa.QualityTools.CodeAnalysis.Core.Models
{
    public abstract class CodeAnalyzerBase<TReport, TResult> : ICodeAnalyzer
        where TReport : ICodeAnalysisReport, new()
    {
        public string CommandPath { get; set; }

        public string CommandOption { get; set; }

        public TReport Report { get; private set; }

        public ICollection<string> ReferenceAssemblyPaths { get; private set; }

        protected string MissingAssembliesCachePath { get; private set; }

        protected List<CommandMessage> CommandMessagesCache { get; private set; }


        protected CodeAnalyzerBase()
        {
            Report = new TReport();
            ReferenceAssemblyPaths = new List<string>();
            MissingAssembliesCachePath = Path.Combine(Path.GetTempPath(), "fxcopintegrator_cache_" + Guid.NewGuid().ToString());
            CommandMessagesCache = new List<CommandMessage>();
        }


        protected abstract void ResolveMissingAssemblies();

        protected abstract TResult Convert(int commandExitCode);

        protected abstract bool HasAssemblyReferencesError(TResult result);

        protected abstract bool HasAnalysisError(TResult result);

        protected abstract string GetReportPath();

        public TResult PerformCodeAnalysis()
        {
            if (String.IsNullOrWhiteSpace(CommandPath))
            {
                throw new InvalidOperationException(Text.Format(Resources.AnalyzerCommandPathIsInvalid, CommandPath));
            }
            if (String.IsNullOrWhiteSpace(CommandOption))
            {
                throw new InvalidOperationException(Text.Format(Resources.AnalyzerCommandOptionIsInvalid, CommandOption));
            }

            try
            {
                Directory.CreateDirectory(MissingAssembliesCachePath);

                var errorCode = PerformCodeAnalysisCore();
                if (HasAnalysisError(errorCode))
                {
                    throw new CodeAnalysisException(Text.Format(Resources.FxCopCmdFailed, errorCode));
                }
                LoadCodeAnalysisReport();

                return errorCode;
            }
            finally
            {
                Directory.Delete(MissingAssembliesCachePath, true);
            }
        }

        public void LoadCodeAnalysisReport()
        {
            Report.Load(GetReportPath());
        }

        private TResult PerformCodeAnalysisCore()
        {
            var exitCode = ExecuteCommand();
            var errorCode = Convert(exitCode);

            Logger.Info("The code analysis command '{0}' returns '{1}'.", Path.GetFileName(CommandPath), errorCode);

            if (HasAssemblyReferencesError(errorCode))
            {
                ResolveMissingAssemblies();
                errorCode = PerformCodeAnalysisCore();
            }

            return errorCode;
        }

        private void FlushCommandMessages()
        {
            CommandMessagesCache.Clear();
        }

        private int ExecuteCommand()
        {
            FlushCommandMessages();

            var option = CommandOption + " /d:" + '"' + MissingAssembliesCachePath + '"';
            var process = new Process();
            process.StartInfo = new ProcessStartInfo(CommandPath, option);
            process.StartInfo.UseShellExecute = false;
            process.StartInfo.CreateNoWindow = true;
            process.StartInfo.RedirectStandardOutput = true;
            process.StartInfo.RedirectStandardError = true;

            process.OutputDataReceived += (sender, e) =>
            {
                if (!String.IsNullOrWhiteSpace(e.Data))
                {
                    CommandMessagesCache.Add(new CommandMessage(CommandMessage.MessageCategory.Info, e.Data));
                    Logger.Info("STDOUT: {0}", e.Data);
                }
            };
            process.ErrorDataReceived += (sender, e) =>
            {
                if (!String.IsNullOrWhiteSpace(e.Data))
                {
                    CommandMessagesCache.Add(new CommandMessage(CommandMessage.MessageCategory.Error, e.Data));
                    Logger.Error("STDERR: {0}", e.Data);
                }
            };

            Logger.Info("Performing code analysis (command = {0} {1}).", CommandPath, option);

            process.Start();
            process.BeginOutputReadLine();
            process.BeginErrorReadLine();
            process.WaitForExit();

            return process.ExitCode;
        }
    }
}
